﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Aeon.Emulator.Instructions;

namespace Aeon.Emulator.Decoding
{
    /// <summary>
    /// Generates all of the instances and methods needed to emulate the x86 instruction set.
    /// </summary>
    internal sealed class InstructionSetBuilder
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the InstructionSetBuilder class.
        /// </summary>
        public InstructionSetBuilder()
        {
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets all of the normal one-byte opcodes.
        /// </summary>
        public IEnumerable<InstructionInfo> OneByteOpcodes
        {
            get
            {
                foreach(var info in opcodes)
                {
                    if(!info.IsMultiByte && info.ModRmByte != ModRmInfo.OnlyRm)
                        yield return info;
                }
            }
        }
        /// <summary>
        /// Gets all of the opcodes where part of the opcode is in the ModR/M byte.
        /// </summary>
        public IEnumerable<InstructionInfo> ExtendedOpcodes
        {
            get
            {
                foreach(var info in opcodes)
                {
                    if(info.ModRmByte == ModRmInfo.OnlyRm && !info.IsMultiByte)
                        yield return info;
                }
            }
        }
        /// <summary>
        /// Gets all of the normal two-byte opcodes.
        /// </summary>
        public IEnumerable<InstructionInfo> TwoByteOpcodes
        {
            get
            {
                foreach(var info in opcodes)
                {
                    if(info.IsMultiByte)
                        yield return info;
                }
            }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Generates instruction set data.
        /// </summary>
        public void BuildSet()
        {
            foreach(MethodInfo methodInfo in FindMethods())
            {
                foreach(InstructionInfo inst in GetMethodInstructions(methodInfo))
                    AddInstruction(inst, methodInfo);
            }
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Adds an instruction to the internal list.
        /// </summary>
        /// <param name="inst">Instruction to add.</param>
        /// <param name="emulateMethod">Method invoked to emulate the instruction.</param>
        private void AddInstruction(InstructionInfo inst, MethodInfo emulateMethod)
        {
            foreach(var subInst in inst.Expand())
            {
                var newInst = subInst;
                newInst.NewEmulators = emulatorBuilder.GetDelegates(subInst);
                opcodes.Add(newInst);
            }
        }
        /// <summary>
        /// Gets all of the emulate methods in the assembly.
        /// </summary>
        /// <returns>Emulate methods found in the assembly.</returns>
        private IEnumerable<MethodInfo> FindMethods()
        {
            Type[] allTypes = Assembly.GetCallingAssembly().GetTypes();
            foreach(Type t in allTypes)
            {
                MethodInfo[] methods = t.GetMethods(BindingFlags.Static | BindingFlags.Public);
                foreach(var methodInfo in methods)
                {
                    object[] res = methodInfo.GetCustomAttributes(typeof(OpcodeAttribute), false);
                    if(res != null && res.Length > 0)
                        yield return methodInfo;
                }
            }
        }
        #endregion

        #region Private Static Methods
        /// <summary>
        /// Return the instructions generated from a method's Opcode attribute.
        /// </summary>
        /// <param name="emulateMethod">Emulate method to generate instructions from.</param>
        /// <returns>Instructions generated from the Opcode attribute on the method.</returns>
        private static IEnumerable<InstructionInfo> GetMethodInstructions(MethodInfo emulateMethod)
        {
            var attrs = emulateMethod.GetCustomAttributes(typeof(OpcodeAttribute), false).Cast<OpcodeAttribute>();
            foreach(OpcodeAttribute attr in attrs)
            {
                string[] codes = attr.OpcodeFormat.Split('|');
                foreach(string code in codes)
                {
                    InstructionInfo info = InstructionInfo.Parse(code.Trim());
                    info.EmulateMethods = GetEmulatorMethods(emulateMethod, attr);
                    if(attr.Name != null)
                        info.Name = attr.Name;
                    else
                    {
                        string className = emulateMethod.DeclaringType.Name;
                        info.Name = className.ToLower();
                    }

                    info.IsPrefix = attr.IsPrefix;
                    info.Epilog = attr.Epilog;
                    
                    yield return info;
                }
            }
        }
        /// <summary>
        /// Returns the set of four emulate methods for an instruction.
        /// </summary>
        /// <param name="defaultMethod">Default method for the instruction.</param>
        /// <param name="opcodeAttribute">Opcode attribute on the emulate method.</param>
        /// <returns>Four emulate methods for the instruction.</returns>
        private static MethodInfo[] GetEmulatorMethods(MethodInfo defaultMethod, OpcodeAttribute opcodeAttribute)
        {
            MethodInfo[] result = new MethodInfo[4];
            // Make sure the array is populated with sizes supported by the default method.
            if((opcodeAttribute.OperandSize & 16) == 16 && (opcodeAttribute.AddressSize & 16) == 16)
                result[0] = defaultMethod;
            if((opcodeAttribute.OperandSize & 32) == 32 && (opcodeAttribute.AddressSize & 16) == 16)
                result[1] = defaultMethod;
            if((opcodeAttribute.OperandSize & 16) == 16 && (opcodeAttribute.AddressSize & 32) == 32)
                result[2] = defaultMethod;
            if((opcodeAttribute.OperandSize & 32) == 32 && (opcodeAttribute.AddressSize & 32) == 32)
                result[3] = defaultMethod;

            // Look for altnerate methods defined in the same class.
            var otherMethods = defaultMethod.DeclaringType.GetMethods(BindingFlags.Static | BindingFlags.Public);
            foreach(var method in otherMethods)
            {
                object[] attrObjects = method.GetCustomAttributes(typeof(AlternateAttribute), false);
                if(attrObjects != null && attrObjects.Length == 1)
                {
                    AlternateAttribute alt = (AlternateAttribute)attrObjects[0];
                    if(alt.MethodName == defaultMethod.Name)
                    {
                        if((alt.OperandSize & 16) == 16 && (alt.AddressSize & 16) == 16)
                            result[0] = method;
                        if((alt.OperandSize & 32) == 32 && (alt.AddressSize & 16) == 16)
                            result[1] = method;
                        if((alt.OperandSize & 16) == 16 && (alt.AddressSize & 32) == 32)
                            result[2] = method;
                        if((alt.OperandSize & 32) == 32 && (alt.AddressSize & 32) == 32)
                            result[3] = method;
                    }
                }
            }

            return result;
        }
        #endregion

        #region Private Fields
        private readonly List<InstructionInfo> opcodes = new List<InstructionInfo>();
        private readonly Decoding.EmulatorBuilder emulatorBuilder = new Decoding.EmulatorBuilder();
        #endregion
    }
}
